package matrix;

////import java.util.Collection;
////import java.util.Iterator;

/**
 * Реализация {@linkplain matrix.IRange Области} в духе шаблона "Компоновщик", где {@linkplain matrix.ICell Ячейки} - "листья", а {@linkplain matrix.IRange (под)Области} - "комбинации". Коренем дерева Компоновщика является Корневая Область, которая обеспечивает хранение Ячеек.
 * <p>Класс {@linkplain matrix.CCompositeRange CCompositeRange} представляет два конструктора: 
 * <li> для {@linkplain matrix.CCompositeRange#CCompositeRange(int, int, ICellFactory, IRangeStorage) создания Корневой} {@linkplain matrix.IRange Области}, которая обеспечивает {@linkplain matrix.IRangeStorage хранение}
 *  {@linkplain matrix.ICellFactory заданных} {@linkplain matrix.ICell Ячеек} 
 * <li> для {@linkplain matrix.CCompositeRange#CCompositeRange(IRange, int, int, int, int) создания Подобласти} внутри Корневой Области или её Подобластей. 
 * </p>
 * @see {@literal Фримен Эр., Фримен Эл. - "Паттерны проектирования", глава 9} 
 */
public class CCompositeRange implements IRange {
	private CCompositeRange m_oParent;
	private int m_iRow;
	private int m_iColumn;
	private int m_iRows;
	private int m_iColumns;
	private ICellFactory m_oCellFactory;
	private IRangeStorage m_oRangeStorage;
	
	/**
	 * Коструктор Корневой (исходной) Области.
	 * <p>Создаёт новую {@linkplain matrix.IRange Корневую Область} с указанным количеством строк и столбцов. 
	 * <p>Заполняет {@linkplain matrix.IRangeStorage Хранилище} {@linkplain matrix.ICellFactory заданными} {@linkplain matrix.ICell Ячейками}
	 * @param aRows int - количество строк
	 * @param aColumns int - количество столбцов
	 * @param aCellFactory {@linkplain matrix.ICellFactory ICellFactory} - фабрика ячеек 
	 * @param aRangeStorage {@linkplain matrix.IRangeStorage IRangeStorage} - хранилище ячеек
	 */
	public CCompositeRange(int aRows, int aColumns, ICellFactory aCellFactory, IRangeStorage aRangeStorage) {
		this.m_oParent = null;
		this.m_iRow = 1;
		this.m_iColumn = 1;
		this.m_iRows = aRows;
		this.m_iColumns = aColumns;
		this.m_oCellFactory = aCellFactory;
		this.m_oRangeStorage = aRangeStorage;
		for (int r = 1; r <= m_iRows; r++) {
			for (int c = 1; c <= m_iColumns; c++) {
				m_oRangeStorage.addCell(m_oCellFactory.createCell(this, r, c), r, c);
			}
		}
	}

	/**
	 * Конструктор подобласти. Создаёт новую {@linkplain matrix.IRange Подобласть} указанного размера внутри заданной (родительской) {@linkplain matrix.IRange Области}
	 * @param aParent родительская {@linkplain matrix.CCompositeRange Область}
	 * @param aRow верхняя строка родительской {@linkplain matrix.IRange Области}
	 * @param aColumn левый столбец родительской {@linkplain matrix.IRange Области}
	 * @param aRows количество строк
	 * @param aColumns количество столбцов
	 * @throws IllegalArgumentException когда:
	 * <li>aParent == null
	 * <li>aRow, aColumn, aRows, aColumns меньше 1 или
	 * <li>aRow+aRows-1 больше aParent.{@linkplain matrix.IRange#rows() rows()} или
	 * <li>aColumn+aColumns-1 больше aParent.{@linkplain matrix.IRange#columns() columns()}
	 */
	public CCompositeRange(CCompositeRange aParent, int aRow, int aColumn, int aRows, int aColumns) {
		if (aParent == null) {
			throw new IllegalArgumentException("aParent can't be null");
		}
		if ((aRow < 1) || (aColumn < 1) || (aRows < 1) || (aColumns < 1)) {
			throw new IllegalArgumentException("aRow, aColumn, aRows, aColumns can't be < 1");
		}
		if ((aRow+aRows-1 > aParent.rows()) || (aColumn+aColumns-1 > aParent.columns())) {
			throw new IllegalArgumentException("aRow+aRows-1 (aColumn+aColumns-1) can't be > aParent.rows() (aParent.columns())");
		}
		this.m_oParent = aParent;
		this.m_iRow = aRow;
		this.m_iColumn = aColumn;
		this.m_iRows = aRows;
		this.m_iColumns = aColumns;
		this.m_oCellFactory = m_oParent.getCellFactory();
		this.m_oRangeStorage = m_oParent.getRangeStorage();
	}

	/**
	 * Этот метод используется потомками для доступа к фабрике ячеек.
	 * @return {@linkplain matrix.ICellFactory Фабрика} {@linkplain matrix.ICell Ячеек}, которыми заполняется {@linkplain matrix.IRange Область} 
	 */
	protected final ICellFactory getCellFactory() {
////		if (m_oParent != null) {
////			return ((CCompositeRange)m_oParent).getCellFactory();
////		}
		return m_oCellFactory;
	}
	/**
	 * Этот метод используется потомками для доступа к хранилищу ячеек.
	 * @return {@linkplain matrix.IRangeStorage Хранилище} {@linkplain matrix.ICell Ячеек} 
	 */
	protected final IRangeStorage getRangeStorage() {
////		if (m_oParent != null) {
////			return ((CCompositeRange)m_oParent).getRangeStorage();
////		}
		return m_oRangeStorage;
	}
	
	@Override
	public final void clear(){
		IRangeIterator i = iterator();
		while (i.hasNext()) {
			i.next().clear();
		}
	}
	/**
	 * @return {@linkplain matrix.CSimpleRangeIterator CSimpleRangeIteratror}
	 */
	public IRangeIterator iterator() {
		return m_oRangeStorage.iterator(row(), column(), m_iRows, m_iColumns);
	}

	/*IRange implementation*/	
	@Override
	public final IRange parent() {
		return m_oParent;
	}

	@Override
	public final int row() {
		if (m_oParent != null) {
			return m_oParent.row() + m_iRow - 1; 
		}
		return m_iRow;
	}

	@Override
	public final IRange row(int aRow) {
		return range(aRow, 1, 1, columns());
	}

	@Override
	public final int column() {
		if (m_oParent != null) {
			return m_oParent.column() + m_iColumn - 1; 
		}
		return m_iColumn;
	}

	@Override
	public final IRange column(int aColumn) {
		return range(1, aColumn, rows(), 1);
	}

	@Override
	public final int rows() {
		return m_iRows;
	}

	@Override
	public final int columns() {
		return m_iColumns;
	}

	@Override
	public IRange range(int aRow, int aColumn, int aRows, int aColumns){
		return new CCompositeRange(this, aRow, aColumn, aRows, aColumns);
	}

	@Override
	public final ICell cell(int aRow, int aColumn) {
		if ((aRow < 1) || (aColumn < 1)) {
			throw new IllegalArgumentException("aRow, aColumn can't be < 1");
		}
		if ((aRow > this.rows()) || (aColumn > this.columns())) {
			throw new IllegalArgumentException("aRow (aColumn) can't be > rows() (columns())");
		}

		return m_oRangeStorage.getCell(row()+aRow-1, column()+aColumn-1);
	}

}
